1   /*
2    * Copyright (C) 2008 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect.testing;
18  
19  import static com.google.common.collect.Lists.newArrayList;
20  import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE;
21  import static java.util.Collections.emptyList;
22  
23  import com.google.common.annotations.GwtCompatible;
24  import com.google.common.collect.ImmutableList;
25  import com.google.common.collect.Lists;
26  
27  import junit.framework.AssertionFailedError;
28  import junit.framework.TestCase;
29  
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.NoSuchElementException;
33  
34  /**
35   * Unit test for IteratorTester.
36   *
37   * @author Mick Killianey
38   */
39  @GwtCompatible
40  @SuppressWarnings("serial") // No serialization is used in this test
41  public class IteratorTesterTest extends TestCase {
42  
43    public void testCanCatchDifferentLengthOfIteration() {
44      IteratorTester<Integer> tester =
45          new IteratorTester<Integer>(4, MODIFIABLE, newArrayList(1, 2, 3),
46              IteratorTester.KnownOrder.KNOWN_ORDER) {
47            @Override protected Iterator<Integer> newTargetIterator() {
48              return Lists.newArrayList(1, 2, 3, 4).iterator();
49            }
50          };
51      assertFailure(tester);
52    }
53  
54    public void testCanCatchDifferentContents() {
55      IteratorTester<Integer> tester =
56          new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2, 3),
57              IteratorTester.KnownOrder.KNOWN_ORDER) {
58            @Override protected Iterator<Integer> newTargetIterator() {
59              return Lists.newArrayList(1, 3, 2).iterator();
60            }
61          };
62      assertFailure(tester);
63    }
64  
65    public void testCanCatchDifferentRemoveBehaviour() {
66      IteratorTester<Integer> tester =
67          new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2),
68              IteratorTester.KnownOrder.KNOWN_ORDER) {
69            @Override protected Iterator<Integer> newTargetIterator() {
70              return ImmutableList.of(1, 2).iterator();
71            }
72          };
73      assertFailure(tester);
74    }
75  
76    public void testUnknownOrder() {
77      new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2),
78          IteratorTester.KnownOrder.UNKNOWN_ORDER) {
79        @Override protected Iterator<Integer> newTargetIterator() {
80          return newArrayList(2, 1).iterator();
81        }
82      }.test();
83    }
84  
85    public void testUnknownOrderUnrecognizedElement() {
86      IteratorTester<Integer> tester =
87          new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2, 50),
88              IteratorTester.KnownOrder.UNKNOWN_ORDER) {
89            @Override protected Iterator<Integer> newTargetIterator() {
90              return newArrayList(2, 1, 3).iterator();
91            }
92          };
93      assertFailure(tester);
94    }
95  
96    /**
97     * This Iterator wraps another iterator and gives it a bug found
98     * in JDK6.
99     *
100    * <p>This bug is this: if you create an iterator from a TreeSet
101    * and call next() on that iterator when hasNext() is false, so
102    * that next() throws a NoSuchElementException, then subsequent
103    * calls to remove() will incorrectly throw an IllegalStateException,
104    * instead of removing the last element returned.
105    *
106    * <p>See
107    * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6529795">
108    * Sun bug 6529795</a>
109    */
110   static class IteratorWithSunJavaBug6529795<T> implements Iterator<T> {
111     Iterator<T> iterator;
112     boolean nextThrewException;
113     IteratorWithSunJavaBug6529795(Iterator<T> iterator) {
114       this.iterator = iterator;
115     }
116 
117     @Override
118     public boolean hasNext() {
119       return iterator.hasNext();
120     }
121 
122     @Override
123     public T next() {
124       try {
125         return iterator.next();
126       } catch (NoSuchElementException e) {
127         nextThrewException = true;
128         throw e;
129       }
130     }
131 
132     @Override
133     public void remove() {
134       if (nextThrewException) {
135         throw new IllegalStateException();
136       }
137       iterator.remove();
138     }
139   }
140 
141   public void testCanCatchSunJavaBug6529795InTargetIterator() {
142     try {
143       /* Choose 4 steps to get sequence [next, next, next, remove] */
144       new IteratorTester<Integer>(4, MODIFIABLE, newArrayList(1, 2),
145           IteratorTester.KnownOrder.KNOWN_ORDER) {
146         @Override protected Iterator<Integer> newTargetIterator() {
147           Iterator<Integer> iterator = Lists.newArrayList(1, 2).iterator();
148           return new IteratorWithSunJavaBug6529795<Integer>(iterator);
149         }
150       }.test();
151     } catch (AssertionFailedError e) {
152       return;
153     }
154     fail("Should have caught jdk6 bug in target iterator");
155   }
156 
157   public void testCanWorkAroundSunJavaBug6529795InTargetIterator() {
158     IteratorTester<Integer> tester =
159         new IteratorTester<Integer>(4, MODIFIABLE, newArrayList(1, 2),
160             IteratorTester.KnownOrder.KNOWN_ORDER) {
161           @Override protected Iterator<Integer> newTargetIterator() {
162             Iterator<Integer> iterator = Lists.newArrayList(1, 2).iterator();
163             return new IteratorWithSunJavaBug6529795<Integer>(iterator);
164           }
165         };
166 
167     /*
168      * Calling this method on an IteratorTester should avoid flagging
169      * the bug exposed by the preceding test.
170      */
171     tester.ignoreSunJavaBug6529795();
172 
173     tester.test();
174   }
175 
176   private static final int STEPS = 3;
177   static class TesterThatCountsCalls extends IteratorTester<Integer> {
178     TesterThatCountsCalls() {
179       super(STEPS, MODIFIABLE, newArrayList(1),
180           IteratorTester.KnownOrder.KNOWN_ORDER);
181     }
182     int numCallsToNewTargetIterator;
183     int numCallsToVerify;
184     @Override protected Iterator<Integer> newTargetIterator() {
185       numCallsToNewTargetIterator++;
186       return Lists.newArrayList(1).iterator();
187     }
188     @Override protected void verify(List<Integer> elements) {
189       numCallsToVerify++;
190       super.verify(elements);
191     }
192   }
193 
194   public void testVerifyGetsCalled() {
195     TesterThatCountsCalls tester = new TesterThatCountsCalls();
196 
197     tester.test();
198 
199     assertEquals("Should have verified once per stimulus executed",
200         tester.numCallsToVerify,
201         tester.numCallsToNewTargetIterator * STEPS);
202   }
203 
204   public void testVerifyCanThrowAssertionThatFailsTest() {
205     final String message = "Important info about why verify failed";
206     IteratorTester<Integer> tester =
207         new IteratorTester<Integer>(1, MODIFIABLE, newArrayList(1, 2, 3),
208             IteratorTester.KnownOrder.KNOWN_ORDER) {
209           @Override protected Iterator<Integer> newTargetIterator() {
210             return Lists.newArrayList(1, 2, 3).iterator();
211           }
212 
213           @Override protected void verify(List<Integer> elements) {
214             throw new AssertionFailedError(message);
215           }
216         };
217     AssertionFailedError actual = null;
218     try {
219       tester.test();
220     } catch (AssertionFailedError e) {
221       actual = e;
222     }
223     assertNotNull("verify() should be able to cause test failure", actual);
224     assertTrue("AssertionFailedError should have info about why test failed",
225         actual.getCause().getMessage().contains(message));
226   }
227 
228   public void testMissingException() {
229     List<Integer> emptyList = newArrayList();
230 
231     IteratorTester<Integer> tester =
232         new IteratorTester<Integer>(1, MODIFIABLE, emptyList,
233             IteratorTester.KnownOrder.KNOWN_ORDER) {
234           @Override protected Iterator<Integer> newTargetIterator() {
235             return new Iterator<Integer>() {
236               @Override
237               public void remove() {
238                 // We should throw here, but we won't!
239               }
240               @Override
241               public Integer next() {
242                 // We should throw here, but we won't!
243                 return null;
244               }
245               @Override
246               public boolean hasNext() {
247                 return false;
248               }
249             };
250           }
251         };
252     assertFailure(tester);
253   }
254 
255   public void testUnexpectedException() {
256     IteratorTester<Integer> tester =
257         new IteratorTester<Integer>(1, MODIFIABLE, newArrayList(1),
258             IteratorTester.KnownOrder.KNOWN_ORDER) {
259           @Override protected Iterator<Integer> newTargetIterator() {
260             return new ThrowingIterator<Integer>(new IllegalStateException());
261           }
262         };
263     assertFailure(tester);
264   }
265 
266   public void testSimilarException() {
267     List<Integer> emptyList = emptyList();
268     IteratorTester<Integer> tester =
269         new IteratorTester<Integer>(1, MODIFIABLE, emptyList,
270             IteratorTester.KnownOrder.KNOWN_ORDER) {
271           @Override protected Iterator<Integer> newTargetIterator() {
272             return new Iterator<Integer>() {
273               @Override
274               public void remove() {
275                 throw new IllegalStateException() { /* subclass */};
276               }
277               @Override
278               public Integer next() {
279                 throw new NoSuchElementException() { /* subclass */};
280               }
281               @Override
282               public boolean hasNext() {
283                 return false;
284               }
285             };
286           }
287         };
288     tester.test();
289   }
290 
291   public void testMismatchedException() {
292     List<Integer> emptyList = emptyList();
293     IteratorTester<Integer> tester =
294         new IteratorTester<Integer>(1, MODIFIABLE, emptyList,
295             IteratorTester.KnownOrder.KNOWN_ORDER) {
296           @Override protected Iterator<Integer> newTargetIterator() {
297             return new Iterator<Integer>() {
298               @Override
299               public void remove() {
300                 // Wrong exception type.
301                 throw new IllegalArgumentException();
302               }
303               @Override
304               public Integer next() {
305                 // Wrong exception type.
306                 throw new UnsupportedOperationException();
307               }
308               @Override
309               public boolean hasNext() {
310                 return false;
311               }
312             };
313           }
314         };
315     assertFailure(tester);
316   }
317 
318   private static void assertFailure(IteratorTester<?> tester) {
319     try {
320       tester.test();
321       fail();
322     } catch (AssertionFailedError expected) {
323     }
324   }
325 
326   private static final class ThrowingIterator<E> implements Iterator<E> {
327     private final RuntimeException ex;
328 
329     private ThrowingIterator(RuntimeException ex) {
330       this.ex = ex;
331     }
332 
333     @Override
334     public boolean hasNext() {
335       // IteratorTester doesn't expect exceptions for hasNext().
336       return true;
337     }
338 
339     @Override
340     public E next() {
341       ex.fillInStackTrace();
342       throw ex;
343     }
344 
345     @Override
346     public void remove() {
347       ex.fillInStackTrace();
348       throw ex;
349     }
350   }
351 }